package cn.kgc.commons.advice;

import cn.kgc.commons.annotation.MyCacheable;
import cn.kgc.commons.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * <p></p>
 *
 * @author 李孟鸠
 * @Date: 2022年12月12日10:58
 */
@Aspect
@Component
@Slf4j
public class MyCacheableAdvice {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedissonClient redissonClient;



    @Pointcut("@annotation(cn.kgc.commons.annotation.MyCacheable)")
    public void myCacheAblePointcut() {
        log.info("切点");
    }

    @Around("myCacheAblePointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {
        //1 方法上是否有MyCacheable，如果有的话，从缓存中获得数据，将数据返回。
        //2 如果缓存，将方法的返回值放入到缓存中
        MethodSignature methodSignature = 
                (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        //获得方法上的缓存注解信息
        MyCacheable myCacheable =
                method.getAnnotation(MyCacheable.class);
        //获得方法上缓存注解信息
        String cacheKeyPress = myCacheable.cacheKey(); //spEl表达式

        // 获得目标方法的参数名字
        ParameterNameDiscoverer parameterNameDiscoverer=new DefaultParameterNameDiscoverer();
        //获得方法的参数名
        String[] names = parameterNameDiscoverer.getParameterNames(method);
        //获得方法的参数值
        Object[] args = proceedingJoinPoint.getArgs();
       //解析器对象
        ExpressionParser expressionParser = new SpelExpressionParser();
        //表达式解析为对象
        Expression expression = expressionParser.parseExpression(cacheKeyPress);
        //创建一个上下文对象,将参数的名字和参数的值保存到上下文中，表达式从上下文中获得参数对应的值
        EvaluationContext context=new StandardEvaluationContext();
        for (int i = 0; i < names.length; i++) {
            String name = names[i];
            Object arg=args[i];
            context.setVariable(name,arg);
        }
        //获得表达式的上面的值
        String spelValue = expression.getValue(context).toString();
        String key=myCacheable.cacheName()+":"+spelValue;
        if (redisUtil.hasKey(key)){ //如果缓存中有结果，将结果返回
            Object value=redisUtil.get(key);
            return value;
        }

        getValue(proceedingJoinPoint, myCacheable, key);

        return proceedingJoinPoint.proceed();
    }

    private Object getValue(ProceedingJoinPoint proceedingJoinPoint, MyCacheable myCacheable, String key) throws Throwable {
        String lockKey= key +":lock";  //缓存锁的key
        RReadWriteLock readWriteLock =
                redissonClient.getReadWriteLock(lockKey);
        RLock rLock = readWriteLock.writeLock();
        rLock.lock(myCacheable.time(), myCacheable.timeUnit());
        try {
            if (redisUtil.hasKey(key)){ //如果缓存中有结果，将结果返回

                return redisUtil.get(key);
            }
            //调用目标方法，获得目标方法的返回值
            Object value = proceedingJoinPoint.proceed();
            //将结果保存到缓存中
            redisUtil.set(key,value, myCacheable.time(), myCacheable.timeUnit());
            return value;
        } finally {
            rLock.unlock();
        }
    }

}
